package com.ouc.pampas;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Invoker
{
  private static final Logger log = LoggerFactory.getLogger(Invoker.class);
  @Autowired
  private ConfigManager configManager;
  @Autowired
  private SpringExecutor springExecutor;
  @Autowired
  private HttpExecutor httpExecutor;
  @Autowired
  private DubboExecutor dubboExecutor;
  private Map<Service.ServiceType, Executor<?>> executorMap = Maps.newHashMap();
  
  @PostConstruct
  public void init()
  {
    this.executorMap.put(Service.ServiceType.SPRING, this.springExecutor);
    this.executorMap.put(Service.ServiceType.DUBBO, this.dubboExecutor);
    this.executorMap.put(Service.ServiceType.HTTP, this.httpExecutor);
  }
  
  public Object invoke(String serviceUri, Map<String, Object> params)
  {
    List<String> serviceInfo = Splitters.COLON.splitToList(serviceUri);
    return invoke((String)serviceInfo.get(0), (String)serviceInfo.get(1), params);
  }
  
  public Object invoke(String app, String key, Map<String, Object> params)
  {
    Preconditions.checkNotNull(key, "Service key should not be null");
    Service service = this.configManager.findService(app, key);
    if (service == null)
    {
      log.error("Service not found: app {}, key {}", app, key);
      throw new NullPointerException("Service not found: " + app + ":" + key);
    }
    Executor<?> executor = (Executor)this.executorMap.get(service.getType());
    return executor.exec(service, params);
  }
  
  public Mapping mappingMatch(String app, String url)
  {
    Preconditions.checkNotNull(url, "url should not be null");
    url = normalize(url);
    Set<Mapping> mappings = this.configManager.findMappings(app);
    if (mappings == null) {
      return null;
    }
    List<String> urls = Splitters.COLON.splitToList(url);
    HttpMethod method = HttpMethod.valueOf((String)urls.get(0));
    url = (String)urls.get(1);
    Mapping targetMapping = null;
    int targetMatchCount = 2147483647;
    for (Mapping mapping : mappings)
    {
      int matchCount = match(method, url, mapping);
      if (matchCount != -1)
      {
        if (matchCount == 0)
        {
          targetMapping = mapping;
          break;
        }
        if (matchCount < targetMatchCount)
        {
          targetMatchCount = matchCount;
          targetMapping = mapping;
        }
      }
    }
    return targetMapping;
  }
  
  public Object mappingInvoke(Mapping mapping, String url, Map<String, Object> params)
  {
    Preconditions.checkNotNull(url, "url should not be null");
    Preconditions.checkNotNull(mapping, "mapping should not be null");
    params.putAll(parse(url, mapping.getPattern()));
    return invoke(mapping.getService(), params);
  }
  
  private int match(HttpMethod method, String url, Mapping mapping)
  {
    if ((!mapping.getMethods().contains(HttpMethod.ALL)) && (!mapping.getMethods().contains(method))) {
      return -1;
    }
    String pattern = mapping.getPattern();
    String[] mappingSplits = url.split("/");
    String[] patternSplits = pattern.split("/");
    if (mappingSplits.length != patternSplits.length) {
      return -1;
    }
    int matchCount = 0;
    for (int i = 0; i < mappingSplits.length; i++) {
      if (patternSplits[i].startsWith("{")) {
        matchCount++;
      } else if (!patternSplits[i].equals(mappingSplits[i])) {
        return -1;
      }
    }
    return matchCount;
  }
  
  private Map<String, String> parse(String url, String pattern)
  {
    Map<String, String> patterns = Maps.newHashMap();
    if (!pattern.contains("{")) {
      return patterns;
    }
    List<String> mappingSplits = Splitters.SLASH.splitToList(url);
    List<String> patternSplits = Splitters.SLASH.splitToList(pattern);
    if (mappingSplits.size() != patternSplits.size()) {
      throw new IllegalArgumentException("not matched url");
    }
    for (int i = 0; i < mappingSplits.size(); i++)
    {
      String patternPart = (String)patternSplits.get(i);
      String mappingPart = (String)mappingSplits.get(i);
      if (patternPart.startsWith("{")) {
        patterns.put(patternPart.substring(1, patternPart.length() - 1), mappingPart);
      } else if (!patternPart.equals(mappingPart)) {
        throw new IllegalArgumentException("not matched url");
      }
    }
    return patterns;
  }
  
  private String normalize(String url)
  {
    if (url.startsWith("/")) {
      return url.substring(1);
    }
    return url;
  }
}
